詳細度よりも優先される CSS の Cascade Layers について
MAD 事業部の高橋ゆうきです。
少し前の話になりますが Safari 15.4 がリリースされました。
これにより Chrome や Firefox、Edge などのブラウザではすでに実装されていた CSS の Cascade Layers が Safari でも利用できるようになりました。
ある程度 Safari のバージョンがさらに上がっていけば実際に使っても良さそうなのか判断するためにも、Cascade Layers について整理してみました。
ここでは Cascade Layers について焦点をあてるため、その他登場する用語については理解していることを前提とし説明をしていません。
また、執筆時には Devtools が Styles pane での表示に十分対応しきれていないことから、画面キャプチャについては Chrome Canary での表示となります。
以下は今回のサンプル用に作成したページとソースコードになります。
Cascade Layers について
カスケードは以下の判断基準で優先されます。
- origin (スタイルの宣言出自) と
!important
- context(Shadow DOM)
- style 属性
- レイヤー
- 詳細度
- 出現順序
仕様では下記で記述されています。
レイヤーは詳細度よりも優先されていることがわかります。
div.square { background-color: red; } .square { background-color: blue; }
上記の CSS ではより詳細度の高い div.square
のスタイルが優先され、背景色は red
になります。
それぞれレイヤーで括ってみます。
@layer red { div.square { background-color: red; } } @layer blue { .square { background-color: blue; } }
このように詳細度よりも優先されるレイヤー(より後で記述したレイヤーが優先されます)で記述されているスタイルが優先され、背景色は blue
になります。
:where()
などを使用した詳細度でのスタイルの優先度調整よりも、シンプルでとてもわかりやすいと感じます。
レイヤーの優先順位は Devtools では画像の赤枠の Toggle CSS Layer View ボタンをクリックする、あるいは Layer 名をクリックすると確認できます。Layer 名をクリックすると、クリックした Layer 名がハイライトされます。
数値が大きいものほど優先度が高いレイヤーになります。
レイヤーの優先順
暗黙的なレイヤー
仕様では unlayered や implicit (final) layer と表記されています。これは文字通り、レイヤーを持たないスタイルでレイヤー内ではもっとも優先されます。
.square { background-color: red; } @layer blue { .square { background-color: blue; } }
Devtools では implicit outer layer として表示されています。
入れ子
レイヤーは入れ子にできます。
@layer blue { @layer red { .square { background-color: red; } } }
入れ子はネストされた識別子をフラットなリストとしても記述できます。
@layer blue.red { .square { background-color: purple; } }
以下のようにすると、.square
は何色になるでしょうか。
@layer blue { @layer red { .square { background-color: purple; } } } @layer red { .square { background-color: red; } } @layer blue.red { .square { background-color: purple; } }
入れ子で記述しても、フラットなリストで記述しても同一のレイヤーに追加されます。上記の場合には blue.red
は最初に記述されたレイヤーに追加されるため、背景色は赤色となります。
優先順序を事前に確立する
上記の入れ子の例で背景色を紫にしたい場合には、優先順序を事前に確立させることで実現できます。
@layer blue, red; @layer blue { @layer red { .square { background-color: purple; } } } @layer red { .square { background-color: red; } } @layer blue.red { .square { background-color: purple; } }
匿名レイヤー
レイヤーは名前をつけない匿名のレイヤー(anonymous layer/unnamed layer)を作成できます。ただし、匿名であるがゆえに優先順序を制御したり、スタイルを追加するなどの外側からの参照ができなくなります。
つまり、以下のような例では背景色は赤となります。
@layer { @layer red { .square { background-color: red; } } } @layer red { .square { background-color: blue; } } @layer { @layer red { .square { background-color: red; } } }
匿名なので記述順序以外で優先順位を制御できません。安易に使うと思わぬスタイルの上書きをしてしまいそうなので、使用するには慎重さが求められる印象を受けました。
revert-layer
キーワード
グローバルキーワードにはこれまで以下がありました。
revert-layer
はスタイルを前のレイヤーで指定されたスタイルに戻すことができます。
@layer red { .square { background-color: red; } } @layer blue { .square { background-color: blue; } .reset { background-color: revert-layer; } }
square reset
がクラスに指定されている場合、背景色は赤くなります。
import した CSS にレイヤーを指定する
import した CSS にレイヤーを指定できます。
/* style.css */ @layer red, blue; @import url("./imported.css") layer(blue); @layer red { .square { background-color: red; } }
/* imported.css */ .square { background-color: blue; }
@import
はパフォーマンスに悪影響が出るため、postcss-import などを使うことになりますが、postcss-import ではすでに import 時におけるレイヤーのサポートがされています。
手元で試してみたところ、問題なく以下のように 1 つのファイルにまとめることができていました。サンプルコードでは npm run build:css
でビルドを試すことができます(出力結果の例)。
/* combined.css */ @layer red, blue; @layer blue { .square { background-color: blue; } } @layer red { .square { background-color: red; } }
PostCSS などで結合しない解決策としては以下のようなものが議論されているようです。
まとめ
Safari の最新バージョンで対応したとはいえ、ある程度の古いバージョンへの対応も必要になることがほとんどです。そのため今すぐ使うのであれば Polyfill がほしいところですが、現在作成中のようです。
レイヤーが使えるようになると、CSS フレームワークや UI フレームワークなどのスタイルを変更がシンプルな記述で書き換えることができそうです。
CSS の設計でもこれまで詳細度でスタイルの調整をしてきたものについては、すべてレイヤーで置き換えることができるようになるため、大きな変化が起こりそうな気がしています。
実際に使えるようになるのが楽しみな機能です。